//
//  MenuMeterNetStats.m
//
// 	Reader object for network throughput info
//
//	Copyright (c) 2002-2006 Alex Harper
//
// 	This file is part of MenuMeters.
// 
// 	MenuMeters is free software; you can redistribute it and/or modify
// 	it under the terms of the GNU General Public License as published by
// 	the Free Software Foundation; either version 2 of the License, or
// 	(at your option) any later version.
// 
// 	MenuMeters is distributed in the hope that it will be useful,
// 	but WITHOUT ANY WARRANTY; without even the implied warranty of
// 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// 	GNU General Public License for more details.
// 
// 	You should have received a copy of the GNU General Public License
// 	along with MenuMeters; if not, write to the Free Software
// 	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
// 

#import "MenuMeterNetStats.h"

@implementation MenuMeterNetStats

///////////////////////////////////////////////////////////////
//	
//	init/dealloc
//
///////////////////////////////////////////////////////////////

- (id)init {
	
	// Allow super to init
	self = [super init];
	if (!self) {
		return nil;
	}
	
	// Prefetch the data first time
	[self getNetData:1.0];
	
	// Send on back
	return self;
	
} // init

- (void)dealloc {
	
	// Free our sysctl buffer
	if (sysctlBuffer) {
		free(sysctlBuffer);
	}
	
	// Release data
	if (lastData) {
		[lastData release];
	}
	
	// Super do its thing
	[super dealloc];

} // dealloc

///////////////////////////////////////////////////////////////
//	
//	Net usage info, based mostly on code found in 
//	XResourceGraph which got it in turn from gkrellm.
//	It reads data from the routing tables using sysctl,
//	which, unlike the kernel memory reads used in netstat
// 	and top, does not require root access
//
///////////////////////////////////////////////////////////////

- (NSDictionary *)getNetData:(double)sampleInterval {
	// MIB for sysctl
	int					mib[] = { CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, 0 };
	size_t				newSize;
	// Pointer math
	char				*dataEnd, *currentData;
	// Routing info structures
	struct if_msghdr	*ifmsg;
	struct sockaddr_dl	*sdl;
	// PPP data
	NSDictionary		*pppData = nil;
	// New data
	NSMutableDictionary	*newData = [NSMutableDictionary dictionary];
	NSString			*interfaceName;
	NSDictionary		*oldCount = nil;
	// New values
	unsigned long		lastifin, lastifout, ifin, ifout, deltain, deltaout;
	unsigned long long	lasttotalin, lasttotalout, totalin, totalout;
	double				peak;
	
	// Get sizing info from sysctl
	if (sysctl(mib, 6, NULL, &newSize, NULL, 0) < 0) {
		return nil;
	}
	
	// Make sure current buffer is big enough
	if (!sysctlBuffer || (newSize > sysctlBufferSize)) {
		if (sysctlBuffer) {
			free(sysctlBuffer);
		}
		sysctlBuffer = malloc(newSize);
		sysctlBufferSize = newSize;
	}
	
	// Read in new data
	if (sysctl(mib, 6, sysctlBuffer, &newSize, NULL, 0) < 0) {
		return nil;
	}	
	dataEnd = sysctlBuffer + newSize;
	currentData = sysctlBuffer;
	
	// Step across the returned data
	while (currentData < dataEnd) {
		// Expecting interface data
		ifmsg = (struct if_msghdr *)currentData;
		if (ifmsg->ifm_type == RTM_IFINFO) {
			// Its an interface data block make sure its not a loopback
			if (!(ifmsg->ifm_flags & IFF_LOOPBACK)) {
				 sdl = (struct sockaddr_dl *)(ifmsg + 1);
				 // Only look a link layer items
				 if (sdl->sdl_family == AF_LINK) {
					// Build the interface name to string so we can key off it
					interfaceName = [NSString stringWithCString:sdl->sdl_data];
					// Have we seen this before?
					oldCount = nil;
					if (lastData) {
						oldCount = [lastData objectForKey:interfaceName];
					}
					// We handle PPP connections differently becuase, for reasons I can't claim to understand,
					// the ppp0 interface never reports outbytes. Thus, we have to go through pppconfd
					if ([interfaceName hasPrefix:@"ppp"]) {
					} // End of PPP handling code
					else { // Not a PPP connection
						// If we've seen it and its up use its totals
						if (oldCount && (ifmsg->ifm_flags & IFF_UP)) {
							// Prevent overflows
							lastifin = [[oldCount objectForKey:@"ifin"] unsignedLongValue];
							lastifout = [[oldCount objectForKey:@"ifout"] unsignedLongValue];
							lasttotalin = [[oldCount objectForKey:@"totalin"] unsignedLongLongValue];
							lasttotalout = [[oldCount objectForKey:@"totalout"] unsignedLongLongValue];
							peak = [[oldCount objectForKey:@"peak"] doubleValue];
							if (lastifin > ifmsg->ifm_data.ifi_ibytes) {
								// Overflow, but avoid going negative (shouldn't happen but paranoid)
								if (ULONG_MAX >= lastifin) {
									totalin = lasttotalin + ifmsg->ifm_data.ifi_ibytes + (ULONG_MAX - lastifin + 1);
								}
								else {
									totalin = lasttotalin + ifmsg->ifm_data.ifi_ibytes;
								}
							}
							else {
								totalin = lasttotalin + (ifmsg->ifm_data.ifi_ibytes - lastifin);
							}
							if (lastifout > ifmsg->ifm_data.ifi_obytes) {
								// Overflow, but avoid going negative (shouldn't happen but paranoid)
								if (ULONG_MAX >= lastifout) {
									totalout = lasttotalout + ifmsg->ifm_data.ifi_obytes + (ULONG_MAX - lastifout + 1);
								}
								else {
									totalout = lasttotalout + ifmsg->ifm_data.ifi_obytes;
								}
							}
							else {
								totalout = lasttotalout + (ifmsg->ifm_data.ifi_obytes - lastifout);
							}
							deltain = (totalin > lasttotalin) ? (totalin - lasttotalin) : 0;
							deltaout = (totalout > lasttotalout) ? (totalout - lasttotalout) : 0;
							if (sampleInterval > 0) {
								peak = ((deltaout / sampleInterval) > peak) ? (double)(deltaout / sampleInterval) : peak;
								peak = ((deltain / sampleInterval) > peak) ? (double)(deltain /sampleInterval) : peak;
							}
							[newData setObject:[NSDictionary dictionaryWithObjectsAndKeys:
													[NSNumber numberWithUnsignedLong:ifmsg->ifm_data.ifi_ibytes],
													@"ifin",
													[NSNumber numberWithUnsignedLong:ifmsg->ifm_data.ifi_obytes],
													@"ifout",
													[NSNumber numberWithUnsignedLong:deltain],
													@"deltain",
													[NSNumber numberWithUnsignedLong:deltaout],
													@"deltaout",
													[NSNumber numberWithUnsignedLongLong:totalin],
													@"totalin",
													[NSNumber numberWithUnsignedLongLong:totalout],
													@"totalout",
													[NSNumber numberWithDouble:peak],
													@"peak",
													nil]
									forKey:interfaceName];
						}
						// Store what we can
						else {
							[newData setObject:[NSDictionary dictionaryWithObjectsAndKeys:
													// Paranoia, is this where the neg numbers came from?
													[NSNumber numberWithUnsignedLongLong:(unsigned long long)ifmsg->ifm_data.ifi_ibytes],
													@"totalin",
													[NSNumber numberWithUnsignedLongLong:(unsigned long long)ifmsg->ifm_data.ifi_obytes],
													@"totalout",
													[NSNumber numberWithUnsignedLong:ifmsg->ifm_data.ifi_ibytes],
													@"ifin",
													[NSNumber numberWithUnsignedLong:ifmsg->ifm_data.ifi_obytes],
													@"ifout",
													[NSNumber numberWithDouble:0],
													@"peak",
													nil]
									forKey:interfaceName];
						}			
					} // end of other interface types	
				 }
			}
		}
		// Continue on
		currentData += ifmsg->ifm_msglen;
	} 
			
	// Swap new data for old
	if (lastData) {
		[lastData release];
	}
	lastData = newData;
	[newData retain];
			
	// Construct and return
	return newData;

} // getNetData

@end
